package com.leejxx.backend.controller;

import com.leejxx.backend.config.SecurityUserDetailsService;
import com.leejxx.backend.util.JwtUtil;
import com.leejxx.backend.vo.UserRegisterAndLoginParam;
import com.leejxx.blog.exception.BusinessRuntimeException;
import com.leejxx.blog.service.SmsService;
import com.leejxx.blog.service.UserService;
import com.leejxx.common.consts.MessageConst;
import com.leejxx.common.entity.Result;
import com.leejxx.common.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author LeeJx
 * @ClassName UserController
 * @Description TODO
 * @date 2021/2/9 20:09
 * @Version 1.0
 */
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {

    @DubboReference
    private UserService userService;

    @DubboReference
    private SmsService smsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private SecurityUserDetailsService userDetailsService;

    @RequestMapping("/findUserByUserId/{id}")
    public Result findUserByUserId(@PathVariable Integer id) {
        log.info("[用户查询]id:{}", id);
        User user = userService.findUserByUserId(id);
        return new Result(true, MessageConst.ACTION_SUCCESS, user);
    }

    @RequestMapping(value = "/getUsername", produces = "application/json;charset=utf-8")
    public Result getUsername(HttpServletResponse response) throws IOException {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (null != principal && principal instanceof User) {
            User user = (User) principal;
            return new Result(true, MessageConst.GET_USERNAME_SUCCESS, user.getUsername());
        }
        return new Result(false, MessageConst.GET_USERNAME_FAIL);
    }

    @RequestMapping("/register")
    public Result register(@RequestBody UserRegisterAndLoginParam registerParam) {
        log.info("[用户操作-用户注册]data:{}", registerParam);

        if (!smsService.checkValidateCode("REGISTER", registerParam.getPhoneNumber(), registerParam.getValidateCode())) {
            return new Result(false, MessageConst.VALIDATE_CODE_ERROR);
        }
        userService.userRegister(registerParam.getUsername(), registerParam.getPassword(), registerParam.getPhoneNumber());
        return new Result(true, MessageConst.REGISTER_SUCCESS);
    }

    /**
     * 登录接口，其中flag为0：手机号验证码登录  1：用户名密码登录
     * @param loginParam 登录信息
     * @return
     */
    @PostMapping(value = "/login/{flag}", produces = "application/json;charset=UTF-8")
    public Result login(@RequestBody UserRegisterAndLoginParam loginParam, @PathVariable String flag) {
        log.info("[用户操作-用户登录]data:{}", loginParam);
        System.out.println(flag);
        if (ObjectUtils.isEmpty(loginParam)) {
            return new Result(false, MessageConst.LOGIN_FAIL);
        }
        // TODO: 2021/2/16 type的值需要根据前端发送验证码时请求的接口的type取，目前暂时随便取一个值，后期更改
        // TODO: 2021/2/16 此处代码按理应该为一个单独的service放在provider模块中，但是会存在SecurityUserDetailsService无法注入到dubbo中，主要原因是该类已经继承了Security中的一个接口，此接口无法注入，我想到的有一下两种解决办法
        // TODO: 2021/2/16 1. 直接将login逻辑也写成过滤器注册到过滤链中，不在使用控制器去管理登录，此方法在security中的使用也较为广泛；
        // TODO: 2021/2/16 2. 新写一个接口继承Security中的接口，然后将service使用该接口，也可解决此问题，以下方法完全是因为编写方便。
        String type = "LOGIN";
        String token = null;
        try {
            if ("0".equals(flag)) {
                log.info("手机号验证码登录");

                if (!StringUtils.hasLength(loginParam.getValidateCode()) ||
                        !smsService.checkValidateCode(type, loginParam.getPhoneNumber(), loginParam.getValidateCode())) {
                    throw new BadCredentialsException("手机号或验证码错误");
                }

                String username = userService.findUsernameByPhoneNumber(loginParam.getPhoneNumber());
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);

                User user = userService.findByUsername(username);
                token = JwtUtil.sign(user.getUsername(), String.valueOf(user.getId()));
            }
            if("1".equals(flag)) {
                log.info("用户名密码登录");
                UserDetails userDetails = userDetailsService.loadUserByUsername(loginParam.getUsername());

                String password = "{bcrypt}" + loginParam.getPassword();
                if (!StringUtils.hasLength(password) || !passwordEncoder.matches(password, userDetails.getPassword())) {
                    throw new BadCredentialsException("用户名或密码错误");
                }

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);

                User user = userService.findByUsername(loginParam.getUsername());
                token = JwtUtil.sign(user.getUsername(), String.valueOf(user.getId()));
            }
        } catch (AuthenticationException e) {
            log.info("登录异常：{}", e.getMessage());
            return new Result(false, MessageConst.LOGIN_FAIL);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        Map<String, Object> res = new HashMap<>();
        res.put("data", map);
        return new Result(true, MessageConst.LOGIN_SUCCESS, res);
    }
}
